
(function(){

  var namespace = 'App.Component';

  //
  // import names
  //

  var classList = Basis.CSS.classList;

  var DOM = Basis.DOM;
  var nsData = Basis.Data;
  var nsTree = Basis.Controls.Tree;
  var nsWrapper = Basis.DOM.Wrapper;
  var nsLayout = Basis.Layout;

  var Property = nsData.Property.Property;

  //
  // Main part
  //

  //
  // Data sources
  //

  var allDataSource = new nsData.Dataset.Merge({
    sources: [
      App.Type.Level.all,
      App.Type.Block.all,
      App.Type.Elem.all,
      App.Type.Mod.all
    ]
  });

  var searchResultMatch = new nsData.Dataset;
  var searchResultFilter = new nsData.Dataset.Merge;
  var searchResult = new nsData.Dataset.Merge({
    rule: nsData.Dataset.Merge.INTERSECTION
  })
  var searchResultComplete = new App.Type.Dataset.Complete({
    sources: [
      searchResult
    ]
  });

  var projectTreeDataSource = new nsData.Dataset.Split({
    rule: Function.getter('getParent()'),
    source: allDataSource
  });

  //
  // Search
  //

  // special filter words in search
  var word2ds = {
    '-l': App.Type.Level.all,
    '-b': App.Type.Block.all,
    '-e': App.Type.Elem.all,
    '-m': App.Type.Mod.all
  };

  // property that switch search mode (change data source for project tree)
  var searchMode = new Property(false, {
    change: function(value){
      //projectTreeDataSource.setSource(value ? searchResultComplete : allDataSource);
    }
  }, Boolean)

  // search as is:
  // * changing of search query trigger search mode;
  // * if search mode -> parse search string, and build source list for search
  //   result dataset, which is complete with ancestors to build tree
  var searchQuery = new Property('', {
    change: function(value, oldValue){
      var sources = [];

      if (value)
      {
        // search mode
        var parts = value.qw();
        var filter = [];
        var query = [];

        for (var i = 0, word; word = parts[i++];)
        {
          if (word.charAt(0) == '-')
          {
            if (word2ds[word])
              filter.push(word2ds[word]);
          }
          else
            query.push(word.forRegExp());
        }

        searchResultFilter.setSources(filter);
        if (filter.length)
          sources.push(searchResultFilter);

        if (query = query.join('|'))  // convert to string and test is it empty
        {
          query = new RegExp('(^|\\b)(' + query + ')');  // convert to RegExp
          searchResultMatch.set(allDataSource.getItems().filter(function(item){
            return query.test(item.getName());
          }));
          sources.push(searchResultMatch);
        }
      }

      searchResult.setSources(sources);
      searchMode.set(value && sources.length);
    }
  }, String.trim);

  smr = searchResultMatch;
  sr = searchResult;
  al = allDataSource;
  ds = projectTreeDataSource;

  //
  // Const & functions
  //
  var TYPE_WEIGHT = {
    Level: 0,
    Block: 1,
    Mod: 2,
    Elem: 3,
    Tech: 4
  };

  var sorting = function(node){
    return TYPE_WEIGHT[node.delegate.typeName] + '_' + node.data.id;
  };


 /**
  * Project node actions popup
  */
  var actionPopup = new Basis.Controls.Popup.Menu({
    childNodes: [
      { caption: 'test' }
    ],
    handler: {
      delegateChanged: function(object, oldDelegate){
        if (oldDelegate)
        {
          classList(oldDelegate.element).remove('menuOpened');
        }
        if (this.delegate)
        {
          classList(this.delegate.element).add('menuOpened');
          this.setChildNodes([]
            .concat(this.delegate.contextActions)
            .concat([
              this.ACTION.rename,
              this.ACTION.destroy
            ])
          );
        }
      },
      hide: function(){
        this.setDelegate();
      }
    },

    defaultHandler: function(node){
      if (node)
        App.parser.run(node.cmd());
      this.hide();
    },

    ACTION: {
      setContext: {
        groupId: 'context',
        caption: 'Сделать контекстом',
        cmd: function(){
          return 'cd /' + this.parentNode.target.getId();
        }
      },
      createBlock: {
        groupId: 'create',
        caption: 'Добавить блок',
        handler: function(){
          App.parser.run('create block b-new ' + this.parentNode.target.getCmdPath());
          this.parentNode.hide();
        }
      },
      createElem: {
        groupId: 'create',
        caption: 'Добавить элемент',
        handler: function(){
          App.parser.run('create elem new ' + this.parentNode.target.getCmdPath());
          this.parentNode.hide();
        }
      },
      createMod: {
        groupId: 'create',
        caption: 'Добавить модификатор',
        handler: function(){
          App.parser.run('create mod new ' + this.parentNode.target.getCmdPath());
          this.parentNode.hide();
        }
      },
      rename: {
        caption: 'Переименовать',
        handler: function(){
          App.parser.run('rename ' + this.parentNode.target.getCmdPath());
          this.parentNode.hide();
        }
      },
      destroy: {
        caption: 'Удалить',
        cmd: function(){
          return 'delete ' + this.parentNode.target.getCmdName() + ' ' + this.parentNode.target.getParent().getCmdPath();
        }
      }
    }
  });


 /**
  * @class
  */
  var TechList = nsWrapper.TmplContainer.subclass({
    template:
      '<div class="b-tech-list"/>',

    localSorting: App.Type.TechType.sorting,
    childClass: {
      template:
        '<span class="b-tech-item" event-click="click" draggable="true" event-dragstart="dragstart">' +
          '{titleText}' +
        '</span>',

      action: {
        click: function(){
          App.UrlResolver.setTarget(this.data.id);
        },
        dragstart: function(event){
          var sender = Basis.Event.sender(event);
          event.dataTransfer.setDragImage(sender, sender.offsetWidth/2, sender.offsetHeight/2);
          /*event.dataTransfer.setData('DownloadURL', 
            //'text/css' +
            //':test.css' +
            'text/css:test.css:data:base64,' + Basis.Crypt.Base64.encode(this.data.content)
          );*/
          //webkitURL.createObjectURL(files[i])
          event.dataTransfer.setData('text/plain', this.data.id);
        }
      },

      init: function(config){
        nsWrapper.TmplNode.prototype.init.call(this, config);

        var techType = this.target.getName();

        this.tmpl.titleText.nodeValue = techType;
        classList(this.element).add('b-tech-item_' + techType.replace(/\./g, '_')); // convert dots
      }
    }
  });


 /**
  * @class
  */
  var ProjectNode = nsTree.TreeFolder.subclass({
    titleGetter: Function.getter('target.getName()'),

    localSorting: sorting,
    destroyDataSourceMember: false,

    template: 
      '<li{element} class="Basis-TreeNode">' +
        '<div{content} class="Basis-TreeNode-Title Basis-TreeNode-CanHaveChildren">' +
          '<span class="actions">' +
            '<span event-click="showActions">+</span>' +
          '</span>' +
          '<div{expander} class="Basis-TreeNode-Expander" event-click="toggle"/>' +
          '<span{title} class="Basis-TreeNode-Caption" event-click="select">' +
            '<span{editingBlock} class="NameEditor">{titleText|}</span>' +
            '<!-- {techList} -->' +
          '</span>' +
        '</div>' + 
        '<ul{childNodesElement} class="Basis-TreeNode-Content"/>' + 
      '</li>',

    action: {
      select: function(){
        //this.preferedTech = null;
        //nsTree.TreeNode.prototype.action.select.call(this, event);
        if (this.selectable)
          App.UrlResolver.setTarget(this.data.id);
      },
      showActions: function(event){
        if (actionPopup.delegate !== this)
        {
          var sender = Basis.Event.sender(event);
          actionPopup.setDelegate(this);
          actionPopup.ignoreClickFor = [sender];
          actionPopup.show(sender);
        }
        else
          actionPopup.hide();
      },
      filterKey: function(event){
        var key = Basis.Event.key(event);
        //console.log(event);
      }
    },

    event_childNodesModified: function(node, delta){
      nsTree.TreeFolder.prototype.event_childNodesModified.call(this, node, delta);
      classList(this.tmpl.content).bool('HasChildNodes', this.childNodes.length);
    },

    satelliteConfig: {
      techList: {
        //existsIf: Function.getter('data.hasTech'),
        dataSource: function(owner){
          return App.Type.Tech.splitByOwner.getSubset(owner.data.id, true);
        },
        instanceOf: TechList
      }
    },

    init: function(config){
      this.dataSource = projectTreeDataSource.getSubset(this.delegate, true);
      nsTree.TreeFolder.prototype.init.call(this, config);
    },
    edit: function(){
      classList(this.tmpl.editingBlock).add('editing');
      Basis.DOM.focus(this.tmpl.editingBlock, true);
    }
  });


 /**
  * Project node type map
  */
  var childClassByType = {
    Level: ProjectNode.subclass({
      cssClassName: "Type-Level",
      selectable: false,
      contextActions: [
        actionPopup.ACTION.setContext,
        actionPopup.ACTION.createBlock,
        actionPopup.ACTION.createElem,
        actionPopup.ACTION.createMod
      ]
    }),
    Block: ProjectNode.subclass({
      cssClassName: "Type-Block",
      contextActions: [
        actionPopup.ACTION.setContext,
        actionPopup.ACTION.createBlock,
        actionPopup.ACTION.createElem,
        actionPopup.ACTION.createMod
      ]
    }),
    Elem: ProjectNode.subclass({
      cssClassName: "Type-Elem",
      contextActions: [
        actionPopup.ACTION.createMod
      ]
    }),
    Mod: ProjectNode.subclass({
      cssClassName: "Type-Mod",
      contextActions: [
      ]
    })
  };

 /**
  * BEM object -> node mapper
  */
  var projectNodes = new nsData.KeyObjectMap({
    create: function(key, config){
      return new childClassByType[key.typeName](config);
    },
    getByPath: function(path){
      var object = App.Type.resolveBem(path);
      if (object)
        return this.get(object);
    }
  });

  searchResult.addHandler({
    datasetChanged: function(dataset, delta){
      var array;

      if (array = delta.inserted)
        for (var i = 0, object; object = array[i++];)
          classList(projectNodes.get(object).tmpl.content).add('SearchMatch');

      if (array = delta.deleted)
        for (var i = 0, object; object = array[i++];)
          classList(projectNodes.get(object).tmpl.content).remove('SearchMatch');
    }
  });

  searchResultComplete.addHandler({
    datasetChanged: function(dataset, delta){
      var array;

      if (array = delta.inserted)
        for (var i = 0, object; object = array[i++];)
          classList(projectNodes.get(object).tmpl.content).add('SearchResultVisible');

      if (array = delta.deleted)
        for (var i = 0, object; object = array[i++];)
          classList(projectNodes.get(object).tmpl.content).remove('SearchResultVisible');
    }
  });

 /**
  * Context node
  */
  var contextNode = new nsData.DataObject({
    mark: DOM.createElement('.ContextNodeMark'),
    handler: {
      delegateChanged: function(object, oldDelegate){
        if (this.delegate)
          DOM.insert(this.delegate.tmpl.title, this.mark, 1);
        else
          DOM.remove(this.mark);
      }
    }
  });

 /**
  * Project tree
  */
  var projectTree = new nsTree.Tree({
    id: 'ProjectTree',
    cssClassName: 'project-tree',
    dataSource: projectTreeDataSource.getSubset(null, true),
    localSorting: sorting,
    destroyDataSourceMember: false,
    childFactory: function(config){
      // child nodes will exists disconected from document
      // that's why we need copy childFactory to each of them
      config.childFactory = this.childFactory; 

      // create new child with projectNodes (or fetch from cache)
      return projectNodes.get(config.delegate, config);
    }
  });

  searchMode.addHandler({
    change: function(value){
      classList(projectTree.element).bool('project-tree_mode-search', value);
    }
  });

 /**
  * Module panel
  */
  var projectTreePanel = new nsLayout.VerticalPanelStack({
    id: 'ProjectTreePanel',
    childNodes: [
      {
        id: 'ProjectTreeSearchPanel',
        childNodes: [
          {
            template:
              '<div class="project-info">' +
                '<div{logo} class="project-info__logo"></div>' +
                '<span class="project-info__title">Test project</span>' +
              '</div>'
          },
          {
            template: 
              '<div class="project-tree-search">' +
                '<div class="project-tree-search__field">' +
                  '<input{input} class="project-tree-search__input" placeholder="Поиск..." event-keyup="search" event-change="search" />' +
                  '<span class="project-tree-search__clear-button" event-click="clear" />' +
                '</div>' +
              '</div>',

            action: {
              search: function(){
                searchQuery.set(this.tmpl.input.value);
              },
              clear: function(){
                var input = this.tmpl.input;

                searchQuery.set(input.value = '');
                DOM.focus(input);
              }
            },

            init: function(config){
              this.constructor.prototype.init.call(this, config);
              searchQuery.addHandler({
                change: function(value){
                  classList(this.element).bool('project-tree-search_on', !!value);
                }
              }, this);
            }
          }
        ]
      },
      {
        flex: 1,
        id: 'ProjectTreeWrapper',
        childNodes: [
          projectTree
        ]
      }
    ]
  });

  new App.Ext.Splitter({
    element: projectTreePanel.element
  });

  // attach to path resolver
  App.UrlResolver.addHandler({
    targetChanged: function(target){
      var node = projectNodes.getByPath(target);
      if (node)
        node.select();
    },
    contextChanged: function(context){
      contextNode.setDelegate(projectNodes.getByPath(context));
    }
  });

  //
  // export names
  //

  Basis.namespace(namespace).extend({
    ProjectTree: projectTreePanel
  });

})();